﻿/*=====================================================================
 * Arquivo:	DBMSInfo.cs
 * 
 * Sumário: Definição da classe que armazena as informações necessárias 
 *          à conexão a um determinado DBMS.
 * 
 * Autor:		Siddhartha Argollo
 * 
 * Copyright (C) TecnoTRENDS.  Todos os direitos reservados.
 =====================================================================*/
using System;
using System.Text;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using DaCredito;

namespace Trends.TFC.Data
{
    /// <summary>
    /// Armazena as informações necessárias à conexão a um determinado DBMS. 
    /// </summary>
    [Serializable]
    public class DBMSInfo : ISerializable
    {
        /// <summary>
        /// Define a posição do parâmetro "Password" dentro da lista de configuração 
        /// </summary>
        public const int PASSWORD_INDEX = 0;

        /// <summary>
        /// Define a posição do parâmetro "User" dentro da lista de configuração
        /// </summary>
        public const int USER_INDEX = 1;

        private string isName;
        private Type itConnectionType;
        private Type itDataParameter;
        private string isSeparator;
        private string isUserId;
        private string isPassword;
        private bool ibNamedParameters;
        private string isParameterPrefix;
        private IDictionary<string, string> ioHibernateProperties;
        private PropertyList ioProperties = new PropertyList();

        /// <summary>
        /// Construtor 1.
        /// </summary>
        /// <param name="asName">Nome do DBMS.</param>
        /// <param name="atConnectionType">Tipo do objeto IDbConnection a ser instanciado
        /// para o DBMS.</param>
        /// <param name="atDataParameter">Tipo do objeto IDataParameter a ser instanciado
        /// para o DBMS.</param>
        /// <param name="abNamedParameters">
        /// Indica se o DBMS utiliza Named Parameters, ou seja, se os parâmetros da
        /// consulta são identificados por nome (true) ou por posição (false).
        /// </param>
        /// <param name="asParameterPrefix">
        /// Indica o prefixo que deve ser usado para indicar os parâmetros de uma
        /// consulta ou o símbolo usado como parâmetro caso NamedParameters
        /// seja false.
        /// </param>
        /// <param name="aoDefProps">Possíveis propriedades do DBMS.</param>
        public DBMSInfo(string asName, Type atConnectionType, Type atDataParameter,
          bool abNamedParameters, string asParameterPrefix, ArrayList aoDefProps)
            : this(asName, atConnectionType, atDataParameter, ";", abNamedParameters,
            asParameterPrefix, aoDefProps)
        {
        }

        /// <summary>
        /// Construtor 2.
        /// </summary>
        /// <param name="asName">Nome do DBMS.</param>
        /// <param name="atConnectionType">Tipo do objeto IDbConnection a ser instanciado
        /// para o DBMS.</param>
        /// <param name="atDataParameter">Tipo do objeto IDataParameter a ser instanciado
        /// para o DBMS.</param>
        /// <param name="asSeparator">Separador das propriedades para a conexão.</param>
        /// <param name="abNamedParameters">
        /// Indica se o DBMS utiliza Named Parameters, ou seja, se os parâmetros da
        /// consulta são identificados por nome (true) ou por posição (false).
        /// </param>
        /// <param name="asParameterPrefix">
        /// Indica o prefixo que deve ser usado para indicar os parâmetros de uma
        /// consulta ou o símbolo usado como parâmetro caso NamedParameters
        /// seja false.
        /// </param>
        /// <param name="aoDefProps">Possíveis propriedades do DBMS.</param>
        public DBMSInfo(string asName, Type atConnectionType, Type atDataParameter,
          string asSeparator, bool abNamedParameters, string asParameterPrefix,
          ArrayList aoDefProps)
        {
            this.isName = asName;
            this.itConnectionType = atConnectionType;
            this.itDataParameter = atDataParameter;
            this.isSeparator = asSeparator;
            this.isUserId = (string)aoDefProps[USER_INDEX];
            this.isPassword = (string)aoDefProps[PASSWORD_INDEX];
            this.ibNamedParameters = abNamedParameters;
            this.isParameterPrefix = asParameterPrefix;
            this.ioProperties.AddRange(aoDefProps);
            this.ioProperties.Remove(this.isPassword);
        }

        /// <summary>
        /// Construtor especial para implementar a serialização do objeto.
        /// </summary>
        /// <param name="info">Objeto SerializationInfo.</param>
        /// <param name="context">Objeto StreamingContext.</param>
        protected DBMSInfo(SerializationInfo info, StreamingContext context)
        {
            this.isName = info.GetString("Name");
            this.itConnectionType = (Type)info.GetValue("Type", typeof(Type));
            this.isSeparator = info.GetString("Separator");
            this.ioProperties = (PropertyList)info.GetValue("DefProps", typeof(PropertyList));
        }

        public IDictionary<string, string> HibernateProperties
        {
            get
            {
                if (this.ioHibernateProperties == null)
                    this.ioHibernateProperties = new Dictionary<string, string>();
                return this.ioHibernateProperties;
            }
        }

        /// <summary>
        /// Retorna o nome do DMBS.
        /// </summary>
        /// <returns>
        /// O nome do DMBS.
        /// </returns>
        public string Name
        {
            get { return this.isName; }
        }

        /// <summary>
        /// Retorna a string usada como separador das propriedades de conexão.
        /// </summary>
        /// <returns>
        /// A string usada como separador das propriedades de conexão.
        /// </returns>
        public string Separator
        {
            get { return this.isSeparator; }
        }

        /// <summary>
        /// Retorna o nome da propriedade que identifica o usuário para login.
        /// </summary>
        /// <returns>
        /// O nome da propriedade que identifica o usuário para login.
        /// </returns>
        public string UserId
        {
            get { return this.isUserId; }
        }

        /// <summary>
        /// Retorna o nome da propriedade que identifica a senha para login.
        /// </summary>
        /// <returns>
        /// O nome da propriedade que identifica a senha para login.
        /// </returns>
        public string Password
        {
            get { return this.isPassword; }
        }

        /// <summary>
        /// Retorna as propriedades do DBMS.
        /// </summary>
        public PropertyList Properties
        {
            get { return this.ioProperties; }
        }

        /// <summary>
        /// Indica se o DBMS suporta nomes de parâmetros no SQL.
        /// </summary>
        /// <remarks>
        /// Caso esta propriedade seja falso, o DBMS suporta parâmetros
        /// posicionais.
        /// </remarks>
        public bool NamedParameters
        {
            get { return this.ibNamedParameters; }
        }

        /// <summary>
        /// Retorna o prefixo usado para identificar parâmetros dentro de
        /// um SQL ou o símbolo usado como parâmetro caso NamedParameters
        /// seja false.
        /// </summary>
        public string ParameterPrefix
        {
            get { return this.isParameterPrefix; }
        }

        /// <summary>
        /// Retorna o tipo do objeto nativo responsável pela realização da conexão 
        /// com o DBMS.
        /// </summary>
        /// <returns> 
        /// O tipo do objeto nativo responsável pela realização da conexão.
        /// </returns>
        public Type ConnectionType
        {
            get { return this.itConnectionType; }
        }

        /// <summary>
        /// Retorna o tipo do objeto nativo que implementa a interface IDataParameter
        /// a ser usado com o DBMS.
        /// </summary>
        public Type DataParameter
        {
            get { return this.itDataParameter; }
        }

        /// <summary>
        /// Retorna a contagem de propriedades que podem ser usadas para
        /// a realização da conexão.
        /// </summary>
        /// <returns>
        /// A contagem de propriedades da conexão.
        /// </returns>
        public int DefaultPropCount
        {
            get
            {
                if (this.ioProperties != null)
                    return this.ioProperties.Count - 1;
                return 0;
            }
        }

        /// <summary>
        /// Cria e retorna o objeto nativo responsável pela conexão ao DBMS.
        /// </summary>
        /// <returns>
        /// O objeto nativo responsável pela conexão ao DBMS.
        /// </returns>
        public IDbConnection CreateConnection()
        {
            return (IDbConnection)Activator.CreateInstance(this.ConnectionType);
        }

        /// <summary>
        /// Cria um objeto IDataParameter a partir do nome e seu valor.
        /// </summary>
        /// <param name="asName">Nome do parâmetro.</param>
        /// <param name="aoValue">Valor  do parâmetro.</param>
        /// <returns>Objeto IDataParameter.</returns>
        public IDataParameter CreateParameter(string asName, object aoValue)
        {
            IDataParameter loParam = (IDataParameter)Activator.CreateInstance(this.itDataParameter);
            loParam.ParameterName = asName;
            loParam.Value = aoValue;
            return loParam;
        }

        /// <summary>
        /// Cria um objeto IDataParameter a partir do nome e seu valor.
        /// </summary>
        /// <param name="asName">Nome do parâmetro.</param>
        /// <param name="aoValue">Valor  do parâmetro.</param>
        /// <param name="aeType">Especifica o tipo do parâmetro.</param>
        /// <returns>Objeto IDataParameter.</returns>
        public IDataParameter CreateParameter(string asName, object aoValue, DbType aeType)
        {
            IDataParameter loParam = (IDataParameter)Activator.CreateInstance(this.itDataParameter);
            loParam.ParameterName = asName;
            loParam.Value = aoValue;
            loParam.DbType = aeType;
            return loParam;
        }

        /// <summary>
        /// Cria um objeto IDataParameter a partir do nome e seu valor.
        /// </summary>
        /// <param name="asName">Nome do parâmetro.</param>
        /// <param name="aoValue">Valor  do parâmetro.</param>
        /// <param name="aeType">Especifica o tipo do parâmetro.</param>
        /// <param name="aoDirection">Especifica a direção do parâmetro.</param>
        /// <returns>Objeto IDataParameter.</returns>
        public IDataParameter CreateParameter(string asName, object aoValue, DbType aeType,
          ParameterDirection aoDirection)
        {
            IDataParameter loParam = (IDataParameter)Activator.CreateInstance(this.itDataParameter);
            loParam.ParameterName = asName;
            loParam.Value = aoValue;
            loParam.DbType = aeType;
            loParam.Direction = aoDirection;
            return loParam;
        }

        /// <summary>
        /// Converte os parâmetros genéricos que usados dentro da consulta para os
        /// parâmetros específicos esperados pelo DBMS.
        /// </summary>
        /// <remarks>
        /// <para>Cada DBMS pode usar notações diferentes para indicar parâmetros dentro
        /// de uma consulta SQL. No caso do OLE, por exemplo, os parâmetros são notados
        /// por um sinal de interrogação e são posicionais, enquanto que, para o
        /// SQL Server, cada parâmetro deve ter um nome identicador prefixados pelo
        /// sinal de dois pontos.</para>
        /// <para>Este método, portanto, realiza a conversão de uma notação genérica
        /// para a notação específica do DBMS representado pelo objeto instanciado.
        /// </para>
        /// <para>
        /// A notação genérica usada para indicação de parâmetros deve ser: {param1},
        /// sem espaços entre as chaves e o nome do parâmetro.
        /// </para>
        /// </remarks>
        /// <param name="asSql">Consulta SQL.</param>
        /// <param name="aoParams">Lista de parâmetros.</param>
        /// <returns>O SQL alterado para uso do DBMS.</returns>
        public string ConvertSqlParameters(string asSql, ref IDataParameter[] aoParams)
        {
            char ch;
            char cQuote = '\0';
            int liPos, liStart;
            String lsParamName;
            StringBuilder loBuilder = new StringBuilder();
            List<IDataParameter> loNewParams = null;

            if (!this.ibNamedParameters)
                loNewParams = new List<IDataParameter>();

            for (int i = 0; i < asSql.Length; i++)
            {
                ch = asSql[i];
                // Identifica se uma string foi aberta ou fechada
                if (ch == '"' || ch == '\'')
                {
                    if (cQuote == ch)
                        cQuote = '\0';
                    else
                        cQuote = ch;
                }
                // Analisa o sql se não estiver dentro de uma string
                if (cQuote == '\0')
                {
                    // Verifica se um parâmetro foi aberto
                    if (ch == '{')
                    {
                        liStart = i + 1;
                        // Não pode iniciar com espaços dentro da chave, e deve começar com uma letra
                        if (asSql[liStart] != ' ' && !Char.IsDigit(asSql[liStart]))
                        {
                            // Procura o final do parâmetro
                            for (liPos = liStart; asSql[liPos] != '}' && liPos < asSql.Length; liPos++) /* Nothing to do */ ;
                            if (liPos >= asSql.Length)
                                throw new Exception("teste");
                            lsParamName = asSql.Substring(liStart, liPos - liStart).Trim();
                            // Verifica primeiro se não encontrou uma função
                            if (!lsParamName.Trim().StartsWith("fn "))
                            {
                                // Adiciona o formato correto no sql
                                loBuilder.Append(this.isParameterPrefix);
                                if (this.ibNamedParameters)
                                    loBuilder.Append(lsParamName);
                                else
                                {
                                    // O parâmetro é posicional, então é necessário rearrumar o
                                    // array de parâmetros passados para execução do SQL.
                                    IDataParameter loFindParam = null;
                                    if (aoParams == null)
                                        throw new Exception("teste");
                                        
                                    foreach (IDataParameter loParam in aoParams)
                                        if (loParam.ParameterName == lsParamName)
                                        {
                                            loFindParam = loParam;
                                            break;
                                        }
                                    if (loFindParam == null)
                                       throw new Exception("teste");
                                      
                                    loNewParams.Add(loFindParam);
                                }
                                i = liPos;
                                continue;
                            }
                        }
                    }
                }
                loBuilder.Append(ch);
            }

            // No caso de parâmetros posicionais, retorna o array com a posição
            // correta de cada parâmetro.
            if (loNewParams != null)
                aoParams = loNewParams.ToArray();

            return loBuilder.ToString();
        }

        /// <summary>
        /// Retorna o objeto em seu formato string.
        /// </summary>
        /// <returns>O objeto em seu formato string.</returns>
        public override string ToString()
        {
            return this.isName;
        }

        /// <summary>
        /// Compara este objeto com o recebido.
        /// </summary>
        /// <param name="obj">Objeto a ser comparado.</param>
        /// <returns>True se os objetos forem iguais, false caso contrário.</returns>
        public override bool Equals(object obj)
        {
            if (obj == null || !(obj is DBMSInfo))
                return false;
            if (base.Equals(obj))
                return true;
            return this.isName.Equals(((DBMSInfo)obj).isName);
        }

        /// <summary>
        /// Retorna o HashCode do objeto.
        /// </summary>
        /// <returns>O HashCode do objeto.</returns>
        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }

        #region ISerializable Members

        /// <summary>
        /// Método usado na serialização do objeto.
        /// </summary>
        /// <param name="info">Objeto SerializationInfo.</param>
        /// <param name="context">Objeto StreamingContext.</param>
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Name", this.isName);
            info.AddValue("Type", this.itConnectionType, typeof(Type));
            info.AddValue("Separator", this.isSeparator);
            info.AddValue("DefProps", this.ioProperties);
        }

        #endregion

    } // DBMSInfo

} // Trends.TFC.Data
